/**
 * Copyright (C), 2017-2018, XXX有限公司
 * FileName: HttpClientBuilder
 * Author:   zengjian
 * Date:     2018/7/25 10:20
 * Description: 客户端创建实现类
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package third.spider.transport;

import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.http.Consts;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.MessageConstraints;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.nio.charset.CodingErrorAction;

/**
 * 〈客户端创建实现类〉<br>
 * 〈一句话描述〉
 *
 * @author zengjian
 * @create 2018/7/25 10:20
 */
public class HttpClientBuilder implements PooledObjectFactory<CloseableHttpClient> {

    @Override
    public PooledObject<CloseableHttpClient> makeObject() throws Exception {
        SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(
                null, new TrustSelfSignedStrategy()).build();
        SSLConnectionSocketFactory sslf = new SSLConnectionSocketFactory(
                sslContext, new String[]{"TLSv1"}, null,
                // SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
                SSLConnectionSocketFactory.getDefaultHostnameVerifier()
        );

        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(2000); // 最大连接数
        connectionManager.setDefaultMaxPerRoute(1000); // 默认每个路由的最大连接数,即一个host+port
        // socket配置
        SocketConfig socketConfig = SocketConfig.custom()
                .setTcpNoDelay(true) //设置是否立即发送数据，不用缓冲，默认为false
                .setSoReuseAddress(true) // 是否一个进程关闭socket后，即使它还没释放端口，其它进程可以立即重用
                .setSoTimeout(3000) // 接收数据的等待超时时间，ms
                .setSoLinger(60) // 关闭sockiet后，要么发送完所有数据，要么等待60s，关闭此连接，此时是阻塞的socket.close()
                .setSoKeepAlive(true) // 开启监听是否有效
                .build();

        connectionManager.setDefaultSocketConfig(socketConfig);

        // 消息约束
        MessageConstraints messageConstraints = MessageConstraints.custom()
                .setMaxHeaderCount(200) // 最大消息头
                .setMaxLineLength(2000)
                .build();
        // HTTP connection相关设置
        ConnectionConfig connectionConfig = ConnectionConfig.custom()
                .setMalformedInputAction(CodingErrorAction.IGNORE)
                .setUnmappableInputAction(CodingErrorAction.IGNORE)
                .setCharset(Consts.UTF_8)
                .setMessageConstraints(messageConstraints)
                .build();
        // 一般不用设置
        //connectionManager.setDefaultConnectionConfig(connectionConfig);

        //  request请求设置
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(2 * 1000) // 连接超时时间
                .setSocketTimeout(2 * 1000) // 等待读数据超时时间
                .setConnectionRequestTimeout(500) // 从池中获取连接超时时间
                .setStaleConnectionCheckEnabled(true) // 检查是否为陈旧的链接
                .build();
        // 重试次数
        HttpRequestRetryHandler requestRetryHandler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                // 超过3次不重shi
                if (executionCount>=3){
                    return false;
                }
                // Timeout
                if (exception instanceof InterruptedIOException){
                    return false;
                }
                // unknown host
                if (exception instanceof UnknownHostException){
                    return false;
                }
                // connetion confused
                if (exception instanceof ConnectTimeoutException){
                    return false;
                }
                // ssl handshake exception
                if (exception instanceof SSLException){
                    return false;
                }
                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
                // 如果是幂等的就重试，消息体不带请求体就是幂等的，即get, 其它post put等都是带的
                if (idempotent){
                    return true;
                }
                return false;
            }
        };

        CloseableHttpClient client = HttpClients.custom()
                .setSSLSocketFactory(sslf)
                .setConnectionManager(connectionManager)
                .setDefaultRequestConfig(requestConfig)
                .setRetryHandler(requestRetryHandler)
                .build();
        return new HttpClientWrapper(client);
    }

    @Override
    public void destroyObject(PooledObject<CloseableHttpClient> pooledObject) throws Exception {
        pooledObject.getObject().close();
    }

    @Override
    public boolean validateObject(PooledObject<CloseableHttpClient> pooledObject) {
        return true;
    }

    @Override
    public void activateObject(PooledObject<CloseableHttpClient> pooledObject) throws Exception {

    }

    @Override
    public void passivateObject(PooledObject<CloseableHttpClient> pooledObject) throws Exception {

    }
}